package com.mg.framework.kernel;

import java.text.DecimalFormat;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MV extends SurfaceView implements SurfaceHolder.Callback, Runnable {

	private final int MS_TO_NS = 1000000;
	private final int NO_DELAYS_PER_YIELD = 16;
	private final int MAX_FRAME_SKIPS = 5;
	private long gameStartTime = 0L;
	private long period = 0L;
	private Thread renderThread = null;
	private SurfaceHolder surfaceHolder = null;
	private volatile boolean running = false;
	private volatile Screen screen = null;
	private volatile Canvas canvas = null;

	private Paint paint = null;

	MV(Context context, Screen initScreen) {
		super(context);
		this.period = (long) 1000.0 / 60 * this.MS_TO_NS;
		this.surfaceHolder = this.getHolder();
		this.surfaceHolder.addCallback(this);
		this.screen = initScreen;

		this.paint = new Paint();
		paint.setColor(Color.YELLOW);
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		startGameLoop();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		exitGameLoop();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {

		if (Touchable.class.isInstance(this.screen)
				&& ((Touchable) this.screen).isTouched(event)) {
			((Touchable) this.screen).onTouchEvent(event);
		}
		return true;
	}

	void setScreen(Screen screen) {
		this.screen = screen;
	}

	void startGameLoop() {
		this.running = true;
		this.renderThread = new Thread(this);
		this.renderThread.start();
	}

	void exitGameLoop() {
		if (this.running) {
			this.running = false;
			try {
				this.renderThread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void run() {
		long beforeTime = 0L;
		long afterTime = 0L;
		long timeDiff = 0L;
		long sleepTime = 0L;
		long overSleepTime = 0L;
		int noDelays = 0;
		long excess = 0L;

		this.gameStartTime = System.nanoTime();

		this.prevStatsTime = this.gameStartTime;

		beforeTime = gameStartTime;

		fpsStore = new double[NUM_FPS];
		upsStore = new double[NUM_FPS];
		for (int i = 0; i < NUM_FPS; i++) {
			fpsStore[i] = 0.0;
			upsStore[i] = 0.0;
		}

		while (this.running) {
			this.canvas = this.surfaceHolder.lockCanvas();
			this.screen.gUpdate();
			this.screen.sUpdate(this.canvas);

			this.canvas.drawText(fps_ups_text, 10, 10, paint);
			this.surfaceHolder.unlockCanvasAndPost(this.canvas);

			afterTime = System.nanoTime();
			timeDiff = afterTime - beforeTime;
			sleepTime = (period - timeDiff) - overSleepTime;
			if (sleepTime > 0) {
				try {
					Thread.sleep(sleepTime / this.MS_TO_NS);
				} catch (InterruptedException ex) {
					System.out.println("sleep error");
				}
				overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
			} else {
				excess -= sleepTime;
				overSleepTime = 0L;
				if (++noDelays >= NO_DELAYS_PER_YIELD) {
					Thread.yield();
					noDelays = 0;
				}
			}

			beforeTime = System.nanoTime();

			int skips = 0;
			while ((excess > period) && (skips < MAX_FRAME_SKIPS)) {
				excess -= period;
				this.screen.gUpdate();
				skips++;
			}

			framesSkipped += skips;
			fps_ups_text = storeStats();
		}
	}

	private static long MAX_STATS_INTERVAL = 1000L;
	private static int NUM_FPS = 10;
	private long statsInterval = 0L;
	private long prevStatsTime;
	private long totalElapsedTime = 0L;
	private long frameCount = 0;
	private double fpsStore[];
	private long statsCount = 0;
	private double averageFPS = 0.0;
	private long framesSkipped = 0L;
	private long totalFramesSkipped = 0L;
	private double upsStore[];
	private double averageUPS = 0.0;
	private DecimalFormat df = new DecimalFormat("0.##");
	private String fps_ups_text = "";

	private String storeStats() {
		frameCount++;
		statsInterval += period;
		if (statsInterval >= MAX_STATS_INTERVAL) {
			long timeNow = System.nanoTime();
			long realElapsedTime = timeNow - prevStatsTime;
			totalElapsedTime += realElapsedTime;
			totalFramesSkipped += framesSkipped;
			double actualFPS = 0;
			double actualUPS = 0;
			if (totalElapsedTime > 0) {
				actualFPS = (((double) frameCount / (totalElapsedTime / this.MS_TO_NS)) * 1000L);
				actualUPS = (((double) (frameCount + totalFramesSkipped) / (totalElapsedTime / this.MS_TO_NS)) * 1000L);
			}
			fpsStore[(int) statsCount % NUM_FPS] = actualFPS;
			upsStore[(int) statsCount % NUM_FPS] = actualUPS;
			statsCount = statsCount + 1;

			double totalFPS = 0.0;
			double totalUPS = 0.0;
			for (int i = 0; i < NUM_FPS; i++) {
				totalFPS += fpsStore[i];
				totalUPS += upsStore[i];
			}

			if (statsCount < NUM_FPS) {
				averageFPS = totalFPS / statsCount;
				averageUPS = totalUPS / statsCount;
			} else {
				averageFPS = totalFPS / NUM_FPS;
				averageUPS = totalUPS / NUM_FPS;
			}

			framesSkipped = 0;
			prevStatsTime = timeNow;
			statsInterval = 0L;
			return "Average FPS/UPS: " + df.format(averageFPS) + ", "
					+ df.format(averageUPS);
		}
		return "Average FPS/UPS: " + df.format(averageFPS) + ", "
				+ df.format(averageUPS);
	}
}